/* Multi-dimensional C array allocation */

/*
 * Copyright (c) 2002, Jean-Sebastien Roy (js@jeannot.org)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

static char const rcsid[] =
	"$Id: mdmalloc.c,v 1.1 2006/06/27 15:35:05 bruhns Exp $";

#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#ifdef DEBUG
#include <stdio.h>
#endif
#include "projects/libv0/src/netcdfStruct/mdmalloc.h"

void *mdmalloc(size_t el_size, size_t n, size_t *d)
{
	size_t i, j, k, ptr_space, arr_space, align_space,
		ptr_count, arr_count,	count, step, next;
	void *zone, **ptr;
	
	if(el_size == 0)
	{
#ifdef DEBUG
		fprintf(stderr,"mdmalloc error : el_size == 0\n");
#endif
		return NULL;
	}

	/* Scalar case */
	if(n == 0) return malloc(el_size);

	/* Checks & Sizing*/
	for(arr_count = 1, ptr_count = 0, i=0;i<n;i++)
	{
		if(d[i] == 0)
		{
#ifdef DEBUG
			fprintf(stderr,"mdmalloc error : dimension %d == 0\n",i);
#endif
			return NULL;
		}
		ptr_count += arr_count;
		arr_count *= d[i];
	}
	
	/* Vector case */
	if(n == 1) return malloc(el_size*d[0]);

	/* Multidimensionnal case */
	ptr_count --;

	/* Space & alignement */
	ptr_space = sizeof(void *)*ptr_count;
	arr_space = el_size*arr_count;
	if(ptr_space % el_size)
		align_space = el_size - (ptr_space % el_size);
	else
		align_space = 0;

	/* allocation */
	zone = malloc(ptr_space + align_space + arr_space);
	if(zone == NULL)
		return NULL; /* malloc failed */

	/* ptr initialiation */
	ptr = (void **)zone;
	
	/* ptrs to ptrs */
	for(k = 0, count = 1, i=0;i<(n-2);i++)
	{
		count *= d[i];
		step = d[i+1];
		next = k+count;
		for(j=0;j<count;j++,k++)
			ptr[k] = &ptr[next+j*step];
	}

	/* dim n-1, ptrs to data */
	count *= d[n-2];
	step = arr_space/count;
	for(j=0;j<count;j++,k++)
		ptr[k] = (char *)zone + ptr_space + align_space + j*step;

	return zone;
}

void *vmdmalloc(size_t el_size, size_t n, ...)
{
	size_t i, *d;
	void *zone;
	va_list ap;

	if(n > 0)
	{
		/* Dimensions */
		d = malloc(sizeof(*d)*n);
		if(d == NULL) return NULL; /* malloc failed */

		va_start(ap, n);
		for(i=0;i<n;i++)
			d[i] = va_arg(ap, size_t);
		va_end(ap);
	}
	else d = NULL;

	zone = mdmalloc(el_size, n, d);

	if(n > 0) free(d);
	
	return zone;
}

void *mdfirst(void *array, size_t  n)
{
	size_t dim;

	for(dim = 1 ; dim < n ; dim ++)
		array = *((void **)array);

	return array;
}

void mdcopy(void *dest, void *src, size_t el_size, size_t  n, size_t *d)
{
	size_t size = el_size;
	size_t dim;

	if(el_size == 0)
	{
#ifdef DEBUG
		fprintf(stderr,"mdcopy error : el_size == 0\n");
#endif
		return;
	}

	for(dim = 0 ; dim < n ; dim ++)
	{
		if(d[dim] == 0)
		{
#ifdef DEBUG
			fprintf(stderr,"mdcopy error : dimension %d == 0\n",dim);
#endif
			return;
		}
		size *= d[dim];
	}
	
	memcpy(mdfirst(dest, n), mdfirst(src, n), size);
}

void vmdcopy(void *dest, void *src, size_t el_size, size_t n, ...)
{
	va_list ap;
	size_t size = el_size;
	size_t dim;

	if(el_size == 0)
	{
#ifdef DEBUG
		fprintf(stderr,"vmdcopy error : el_size == 0\n");
#endif
		return;
	}

	if(n > 0)
	{
		size_t c;
		
		va_start(ap, n);
		for(dim = 0 ; dim < n ; dim ++)
		{
			c = va_arg(ap, size_t);
#ifdef DEBUG
			if(c == 0)
				fprintf(stderr,"vmdcopy error : dimension %d == 0\n",dim);
#endif
			size *= c;
		}
		va_end(ap);

		if(size == 0)
			return;
	}

	memcpy(mdfirst(dest, n), mdfirst(src, n), size);
}
